


using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Fido2NetLib;
using Fido2NetLib.Objects;
using UMC.Data;
using UMC.Net;
using UMC.Web;
namespace UMC.ITME.Activities
{

    [UMC.Web.Apiumc("Account", "WebAuthn", Auth = WebAuthType.All)]
    class SiteWebAuthnActivity : UMC.Web.WebActivity
    {
        protected void Auth()
        {
            var cUser = this.Context.Token.Identity();
            var config = UMC.Data.Reflection.Configuration("account");
            var login = config["login"] ?? Provider.Create("login", "name");
            var WebAuthn = Utility.IntParse(WebResource.Instance().Provider["webAuthn"], 0);//"1";
            if (WebAuthn == 0)
            {
                WebAuthn = Utility.IntParse(this.AsyncDialog("WebAuthn", r =>
                {
                    if (String.IsNullOrEmpty(Data.DataFactory.Instance().Password(cUser.Id.Value)) == false || String.IsNullOrEmpty(login["loginUrl"]) == false)
                    {
                        return new UISheetDialog() { Title = "核验方式" }.Put("人脸核验", "1").Put("扫码验证", "3").Put("密码核验", "2"); ;
                    }
                    else
                    {
                        return new UISheetDialog() { Title = "核验方式" }.Put("人脸核验", "1").Put("扫码验证", "3"); ;
                    }
                }), 1);
            }
            switch (WebAuthn)
            {
                default:
                    this.AsyncDialog("Biometric", "System", "Biometric", true);
                    break;
                case 2:
                    {
                        var userManager = UMC.Security.Membership.Instance();
                        var Password = UIDialog.AsyncDialog(this.Context, "Password", d =>
                        {
                            var dialog = new Web.UIFormDialog();
                            dialog.Title = "验证账户";
                            dialog.AddTextValue().Put("账户", cUser.Name).Put("别名", cUser.Alias);
                            dialog.AddPassword("密码", "Password").PlaceHolder("账户密码");
                            dialog.Submit("验证", "WebAuthn.Register");
                            return dialog;
                        });
                        var time = TempKey;// this.AsyncDialog("Time", s => this.DialogValue(Utility.TimeSpan().ToString()));
                        var vcode = this.AsyncDialog("VCode", r =>
                        {
                            if (String.IsNullOrEmpty(Data.DataFactory.Instance().Password(cUser.Id.Value)))
                            {
                                var url = login["loginUrl"];
                                if (String.IsNullOrEmpty(url) == false)
                                {
                                    WebMeta data = new WebMeta().Put("username", cUser.Name, "password", Password);
                                    var post = data.Put("address", this.Context.Request.UserHostAddress);
                                    var tIndex = url.IndexOf("?");
                                    if (tIndex > 0)
                                    {
                                        var vs = url.Substring(tIndex + 1).Split('&');
                                        foreach (var v in vs)
                                        {
                                            var v2 = v.Split('=');
                                            if (v2.Length == 2)
                                            {
                                                post.Put(v2[0], v2[1]);
                                            }
                                        }
                                    }

                                    Stopwatch stopWatch = new Stopwatch();
                                    stopWatch.Start();
                                    var result = new Uri(url).WebRequest().Post(post, out var _);
                                    var webName = JSON.Deserialize<WebMeta>(result);
                                    stopWatch.Stop();
                                    Utility.Log("API", url, stopWatch.Elapsed.ToString(), webName);

                                    if (String.Equals(webName?.Get("code"), "success"))
                                    {
                                        var v = webName["vcode"];
                                        if (String.IsNullOrEmpty(v) == false && this.Context.Request.IsApp == false)
                                        {
                                            this.Context.Request.Arguments.Put("Sign", Utility.IntParse(Utility.MD5(v, time)));
                                            var t = new UITextDialog() { Title = "二次认证" };
                                            t.Config.PlaceHolder(webName["title"]);
                                            return t;
                                        }
                                    }
                                    else
                                    {
                                        this.Prompt(webName?["msg"] ?? "登录Api返回格式不正确");
                                    }

                                }
                                else
                                {
                                    this.Prompt("账户未开启密码认证");
                                }
                            }
                            else if (userManager.Password(cUser.Id.Value, Password, 0, 0, 0) != 0)
                            {
                                this.Prompt("密码不正确");
                            }

                            this.Context.Request.Arguments.Put("Sign", Utility.IntParse(Utility.MD5(cUser.Name, time)).ToString());

                            var totp = UMC.Data.DataFactory.Instance().Account(cUser.Id.Value, Security.Account.TOTP_ACCOUNT_KEY);
                            if (totp != null && totp.Flags != Security.UserFlags.Lock)
                            {
                                var t = new UITextDialog() { Title = "二次认证" };
                                t.Config.PlaceHolder("请输入身份验证器口令");
                                return t;
                            }

                            return this.DialogValue(cUser.Name);

                        });

                        var sign = Utility.IntParse(this.AsyncDialog("Sign", "0"), 0);

                        if (Utility.IntParse(Utility.MD5(cUser.Name, time)) == sign)
                        {
                            var totp = UMC.Data.DataFactory.Instance().Account(cUser.Id.Value, Security.Account.TOTP_ACCOUNT_KEY);
                            if (totp != null && totp.Flags != Security.UserFlags.Lock)
                            {
                                if (UMC.Security.Membership.Instance().Totp(cUser.Id.Value, vcode) == false)
                                {

                                    this.Context.Request.Arguments.Remove("VCode");
                                    this.Prompt("口令不正确", false);
                                    this.Context.Response.Redirect(this.Context.Request.Model, this.Context.Request.Command, new UITextDialog() { Title = "二次认证", DefaultValue = vcode }, this.Context.Request.Arguments, true);
                                }
                            }
                        }
                        else if (Utility.IntParse(Utility.MD5(vcode, time)) != sign)
                        {
                            this.Context.Request.Arguments.Remove("VCode");
                            this.Prompt("口令不正确", false);
                            this.Context.Response.Redirect(this.Context.Request.Model, this.Context.Request.Command, new UITextDialog() { Title = "二次认证", DefaultValue = vcode }, this.Context.Request.Arguments, true);

                        }
                    }
                    break;
                case 3:
                    {
                        var time = TempKey;
                        this.AsyncDialog("ScanAuth", r =>
                          {
                              var request = this.Context.Request;
                              var account = UMC.Data.Reflection.Configuration("account").Providers;
                              var hash = new HashSet<String>();
                              if (String.IsNullOrEmpty(WebResource.Instance().Provider["appId"]) == false)
                              {
                                  hash.Add("移动端");
                              }
                              foreach (var p in account)
                              {
                                  switch (p.Type)
                                  {
                                      case "dingtalk":
                                          hash.Add("钉钉");
                                          break;
                                      case "wxwork":
                                          hash.Add("企业微信");
                                          break;
                                  }
                              }
                              if (hash.Count == 0)
                              {
                                  this.Prompt("未配置移动端、钉钉、企业微信任何一个扫码参数");
                              }
                              var ScanQR = new UIScanQRDialog(new Uri(request.Url, $"/UMC/Account/Scan/{UMC.Data.Utility.Guid(this.Context.Token.Device.Value)}").AbsoluteUri);//UIDialog.Create("ScanQR");
                              ScanQR.Title = "扫码核验账户";
                              ScanQR.Desc = String.Format("请使用{0}来扫码验证账户", String.Join("、", hash));
                              ScanQR.CloseEvent = "ScanQR.OK";
                              ScanQR.DataEvent = "Login";

                              return ScanQR;
                          });
                        var sign = Utility.IntParse(this.AsyncDialog("Sign", r =>
                          {
                              var seesionKey = UMC.Data.Utility.Guid(this.Context.Token.Device.Value);
                              var seesion = UMC.Data.DataFactory.Instance().Session(seesionKey);

                              if (seesion != null)
                              {
                                  var Value = UMC.Data.JSON.Deserialize<UMC.Data.AccessToken>(seesion.Content.Span);
                                  var checkUser = Value.Identity();

                                  UMC.Data.DataFactory.Instance().Delete(seesion);
                                  if (checkUser.Name == cUser.Name)
                                  {
                                      this.Context.Send("ScanQR.OK", false);
                                      return this.DialogValue(Utility.IntParse(Utility.MD5(cUser.Name, time)).ToString());
                                  }
                                  else
                                  {
                                      this.Prompt("用户不一致");
                                  }
                              }
                              else
                              {
                                  this.Prompt("未验证通过");
                              }
                              return this.DialogValue("none");
                          }), 0);
                        if (Utility.IntParse(Utility.MD5(cUser.Name, time)) != sign)
                        {
                            this.Prompt("验证不通过");
                        }
                    }
                    break;
            }
        }

        bool Check(Data.Entities.User user)
        {
            var flags = user.Flags ?? Security.UserFlags.Normal;
            if ((flags & Security.UserFlags.Lock) == Security.UserFlags.Lock)
            {
                this.Prompt("账户已经锁定");
            }
            else if (user.IsDisabled == true)
            {
                this.Prompt("账户已经禁用");
            }
            else if (user.ExpireTime > 0 && user.ExpireTime < Utility.TimeSpan())
            {
                this.Prompt("账户已过期");
            }
            else
            {
                return true;
            }
            return false;
        }
        void Register(Fido2Configuration fido2Configuration)
        {
            var AuthnType = Utility.IntParse(this.AsyncDialog("Authn", r =>
            {
                var WebAuthn = UMC.Security.Account.Create(this.Context.Token.UserId.Value).WebAuthn;
                if (WebAuthn.Length >= 3)
                {
                    this.Prompt("只能设置三个通行密钥");
                }


                var AuthnType = Security.Account.WebAuthn_ACCOUNT_KEY;

                if (WebAuthn.Any(r => r.Type == AuthnType))
                {
                    AuthnType = Security.Account.WebAuthn_ACCOUNT_KEY2;
                    if (WebAuthn.Any(r => r.Type == AuthnType))
                    {
                        AuthnType = Security.Account.WebAuthn_ACCOUNT_KEY3;
                    }

                }


                return AuthnType.ToString();

            }), 0);






            Auth();

            Fido2 fido2 = new Fido2(fido2Configuration);

            var Challenge = Utility.Guid(this.AsyncDialog("Challenge", Utility.Guid(Guid.NewGuid())), true).Value.ToByteArray();
            var user = new Fido2User
            {
                DisplayName = this.Context.Token.Alias,
                Name = this.Context.Token.Alias,
                Id = this.Context.Token.UserId.Value.ToByteArray()
            };
            var authenticator = AuthenticatorSelection.Default;
            authenticator.UserVerification = UserVerificationRequirement.Required;

            var options = new CredentialCreateOptions
            {
                Status = "ok",
                ErrorMessage = string.Empty,
                Challenge = Challenge,
                Rp = new PublicKeyCredentialRpEntity(fido2Configuration.ServerDomain, fido2Configuration.ServerName, fido2Configuration.ServerIcon),
                Timeout = fido2Configuration.Timeout,
                User = user,
                PubKeyCredParams = new List<PubKeyCredParam>()
            {
                PubKeyCredParam.ES256,
                PubKeyCredParam.RS256,
                PubKeyCredParam.PS256,
                PubKeyCredParam.ES384,
                PubKeyCredParam.RS384,
                PubKeyCredParam.PS384,
                PubKeyCredParam.ES512,
                PubKeyCredParam.RS512,
                PubKeyCredParam.PS512,
                PubKeyCredParam.Ed25519
            },
                AuthenticatorSelection = authenticator,
                Attestation = AttestationConveyancePreference.Indirect,
                ExcludeCredentials = new List<PublicKeyCredentialDescriptor>(),
                Extensions = null
            };

            var Json = this.AsyncDialog("Register", g =>
            {

                UMC.Data.DataFactory.Instance().Delete(new Data.Entities.Account
                {
                    Type = AuthnType,
                    user_id = this.Context.Token.UserId.Value
                });
                this.Context.Send("WebAuthn.Register", new WebMeta("name", g).Put("value", JSON.Expression(options.ToJson())).Put("submit", new UIClick(this.Context.Request)), true);
            });
            var m = JSON.Deserialize<Hashtable>(Json);
            if (m == null)
            {
                this.Prompt("Web通行证注册失败");
            }
            var r = m["response"] as Hashtable;
            AuthenticatorAttestationRawResponse attestationRawResponse = new AuthenticatorAttestationRawResponse
            {
                Id = Base64Url.Decode(m["id"] as string),
                RawId = Base64Url.Decode(m["rawId"] as string),
                Response = new AuthenticatorAttestationRawResponse.ResponseData()
                {
                    AttestationObject = Base64Url.Decode(r["attestationObject"] as string),
                    ClientDataJson = Base64Url.Decode(r["clientDataJSON"] as string)
                },
                Type = PublicKeyCredentialType.PublicKey
            };

            var success = fido2.MakeNewCredentialAsync(attestationRawResponse, options, (v, g) => System.Threading.Tasks.Task.FromResult(true)).Result;
            var platform = this.Context.Client.Context.Headers.Get("sec-ch-ua-platform") ?? this.Context.Token.Alias;// String.Empty;
                                                                                                                     //   var m=
                                                                                                                     // var accounts = UMC.Security.Account.Create(this.Context.Token.UserId.Value);
            UMC.Data.Entities.Account account = new Data.Entities.Account()
            {
                Name = Convert.ToBase64String(success.Result.CredentialId),
                user_id = this.Context.Token.UserId.Value,
                Alias = platform.Trim('\"'),
                Type = AuthnType,// Security.Account.WebAuthn_ACCOUNT_KEY,
                Flags = Security.UserFlags.Normal,
                ConfigData = new StringValue(JSON.Serialize(new WebMeta().Put("PublicKey", Convert.ToBase64String(success.Result.PublicKey))))
            };
            UMC.Data.DataFactory.Instance().Put(account);
            this.Context.Send(true);

        }
        void Login(Fido2Configuration fido2Configuration)
        {
            var Challenge = Utility.Guid(this.AsyncDialog("Challenge", Utility.Guid(Guid.NewGuid())), true).Value.ToByteArray();

            Fido2 fido2 = new Fido2(fido2Configuration);


            var d = new PublicKeyCredentialDescriptor[0];

            var options = AssertionOptions.Create(fido2Configuration, Challenge, d, UserVerificationRequirement.Discouraged, null);

            var Json = this.AsyncDialog("Login", g =>
            {
                this.Context.Send("WebAuthn.Login", new WebMeta("name", g).Put("value", JSON.Expression(options.ToJson())).Put("submit", new UIClick(this.Context.Request)), true);
            });
            AuthenticatorAssertionRawResponse rawResponse = new AuthenticatorAssertionRawResponse();
            var m = JSON.Deserialize<Hashtable>(Json);
            var r = m["response"] as Hashtable;

            AuthenticatorAssertionRawResponse attestationRawResponse = new AuthenticatorAssertionRawResponse
            {
                Id = Base64Url.Decode(m["id"] as string),
                RawId = Base64Url.Decode(m["rawId"] as string),
                Response = new AuthenticatorAssertionRawResponse.AssertionResponse()
                {
                    Signature = Base64Url.Decode(r["signature"] as string),
                    AuthenticatorData = Base64Url.Decode(r["authenticatorData"] as string),
                    ClientDataJson = Base64Url.Decode(r["clientDataJSON"] as string)
                },
                Type = PublicKeyCredentialType.PublicKey
            };
            var ns = UMC.Data.DataFactory.Instance().Account(Convert.ToBase64String(attestationRawResponse.Id));

            if (ns.Length == 0)
            {
                this.Prompt("此Web凭证已经注销");
            }
            var pubKey = ns.Select(r =>
            {
                Hashtable hashtable = JSON.Deserialize<Hashtable>(r.ConfigData);
                return Tuple.Create(Convert.FromBase64String(hashtable["PublicKey"] as string), r);
            });

            var publicKey = pubKey.First();//r => r.Item2.Name==(attestationRawResponse.Id));

            var success = fido2.MakeAssertionAsync(attestationRawResponse, options, publicKey.Item1, 0, (v, g) => System.Threading.Tasks.Task.FromResult(true)).Result;
            if (success.Status != "ok")
            {
                this.Prompt("Web凭证", success.ErrorMessage);
            }
            var userManager = UMC.Security.Membership.Instance();
            var user = Data.DataFactory.Instance().User(publicKey.Item2.user_id.Value);
            if (user != null)
            {
                Check(user);
            }
            var identity = userManager.Identity(publicKey.Item2.user_id.Value) ?? UMC.Security.Identity.Create(publicKey.Item2.user_id.Value, "#", publicKey.Item2.Alias ?? "关联用户");

            var config = UMC.Data.Reflection.Configuration("account");

            var login = config["login"] ?? Provider.Create("login", "name");


            var token = this.Context.Token.Login(identity, this.Context.Request.IsApp ? 0 : UMC.Data.Utility.IntParse(login.Attributes["timeout"], 3600));


            token.Commit(this.Context.Request.IsApp ? "App" : "Desktop", true, this.Context.Request.UserHostAddress, this.Context.Server);

            this.Context.Send("User", new WebMeta("Alias", identity.Alias).Put("Src", Data.WebResource.Instance().ImageResolve(this.Context.Request.Url, identity.Id.Value, "1", 5)), true);
            // this.Context.Send("User", true);
        }
        public override void ProcessActivity(WebRequest request, WebResponse response)
        {
            if (request.IsApp)
            {
                this.Prompt("Web凭证只支持在浏览器操作");
            }
            Fido2Configuration fido2Configuration = new Fido2Configuration();

            var Authority = (this.Context.Request.UrlReferrer ?? this.Context.Request.Url).Host;
            fido2Configuration.ServerDomain = Authority;

            fido2Configuration.ServerName = fido2Configuration.ServerDomain;
            fido2Configuration.Origins = new HashSet<string>
            {
                $"https://{fido2Configuration.ServerDomain}",
                Context.Client.Context.Headers.Get("Origin"u8).Span.UTF8()
            };


            var type = this.AsyncDialog("Type", "Register");
            switch (type)
            {
                case "Register":
                    if (request.IsApp)
                    {
                        this.Prompt("Web通行证只支持在浏览器中使用"); break;
                    }
                    this.Register(fido2Configuration);
                    break;
                default:
                    this.Login(fido2Configuration);
                    break;

            }
        }
    }
}